/*
* @Author: Adolphus.Yao
* @Date:   2019-08-29 15:30:41
* @Last Modified by:   Adolphus.Yao
* @Last Modified time: 2019-08-29 21:49:57
*/
package repositories

import (
    "errors"
    "sync"
    "fulnero.com/iris/mvc/datamodels"
)

func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
    return &movieMemoryRepository{source: source}
}

type movieMemoryRepository struct {
    source map[int64]datamodels.Movie
    mu     sync.RWMutex
}

const (
    ReadOnlyMode = iota
    ReadWriteMode
)

func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
    loops := 0
    if ReadOnlyMode == mode {
        r.mu.RLock()
        defer r.mu.RUnlock()
    } else {
        r.mu.Lock()
        defer r.mu.Unlock()
    }
    for _, dao := range r.source {
        ok = query(dao)
        if ok {
            if action(dao) {
                loops++
                if actionLimit >= loops {
                    break
                }
            }
        }
    }
    return
}

func (r *movieMemoryRepository) Select(query Query) (dao datamodels.Dao, found bool) {
    found = r.Exec(query, func(m datamodels.Dao) bool {
        dao = m
        return true
    }, 1, ReadOnlyMode)
    if !found {
        dao = datamodels.Movie{}
    }
    return
}

func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Dao) {
    r.Exec(query, func(m datamodels.Dao) bool {
        results = append(results, m)
        return true
    }, limit, ReadOnlyMode)
    return
}

func (r *movieMemoryRepository) InsertOrUpdate(dao datamodels.Dao) (datamodels.Dao, error) {
    movie := dao.(datamodels.Movie)
    id := movie.ID
    if 0 == id {
        var lastID int64
        r.mu.RLock()
        for _, item := range r.source {
            if item.ID > lastID {
                lastID = item.ID
            }
        }
        r.mu.RUnlock()
        id = lastID + 1
        movie.ID = id
        r.mu.Lock()
        r.source[id] = movie
        r.mu.Unlock()

        return movie, nil
    }
    curDao, exists := r.Select(func(d datamodels.Dao) bool {
        m := d.(datamodels.Movie)
        return m.ID == id
    })
    if !exists {
        return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
    }
    current := curDao.(datamodels.Movie)
    if "" != movie.Poster {
        current.Poster = movie.Poster
    }
    if "" != movie.Genre {
        current.Genre = movie.Genre
    }
    r.mu.Lock()
    r.source[id] = current
    r.mu.Unlock()
    return movie, nil
}

func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
    return r.Exec(query, func(d datamodels.Dao) bool {
        m := d.(datamodels.Movie)
        delete(r.source, m.ID)
        return true
    }, limit, ReadWriteMode)
}
